#include "PairType.h"

#include <cassert>
#include "FunctionList.h"
#include "PropertyList.h"
#include "NativeFieldProperty.h"

#include "PlaceholderType.h"


#include "TypeInstance.h"
#include "GarbageCollectable.h"

CString CPairType::PlaceholderNameKey=U"Key";
CString CPairType::PlaceholderNameValue=U"Value";

CPairType::CPairType()
    :CType(
        STypeID(CType::GetEngineCoreNamespace()+U"::TNativePair",true)
        ,ETypeFlags::Generic | ETypeFlags::Pair
        )
{
    GenericInfo= new CGenericInfo();
    
    //1. create place holder types
    CPlaceholderType* KeyPlaceholderType 
        = new CPlaceholderType(
            STypeID(GetFullName()+U"::"+PlaceholderNameKey,true)
            ,ETypeFlags::None 
            ,0
            ,this );
    CPlaceholderType* ValuePlaceholderType 
        = new CPlaceholderType(
            STypeID(GetFullName()+U"::"+PlaceholderNameValue ,true)
            ,ETypeFlags::None
            ,1
            ,this );

    GenericInfo->PlaceholderTypes.Add(KeyPlaceholderType);
    GenericInfo->PlaceholderTypes.Add(ValuePlaceholderType);

    //add fields
    Properties = new CPropertyList(this);
    {
        //key field
        CProperty* KeyProperty = new CNativeFieldProperty(
            this->Properties
            ,{}
            ,KeyPlaceholderType
            ,U"Key"
            ,0
            ,0
        );

        Properties->AddProperty(KeyProperty);
    }

    {
        //value field
        CProperty* ValueProperty = new CNativeFieldProperty(
            this->Properties
            ,{}
            ,ValuePlaceholderType
            ,U"Value"
            ,0
            ,0
        );

        Properties->AddProperty(ValueProperty);
    }


    Functions= new CFunctionList(this);

    //TODO add functions
}

CPairType *CPairType::GetStaticType()
{
    static CPairType* PairType = new CPairType();
    return PairType;
}


CString CPairType::CreateTypeName(const CString &KeyType, const CString &ValueType)
{
    return STypeID::CreateTemplateTypeName(
        GetStaticType()->GetFullName()
        ,{KeyType,ValueType}
        ,{ PlaceholderNameKey, PlaceholderNameValue }
        );
}

STypeID CPairType::CreateID(const SDecoratedType &KeyType, const SDecoratedType &ValueType)
{
    return STypeID(
        GetStaticType()->ID
        ,{KeyType.BasicType->ID,ValueType.BasicType->ID}
        ,{ PlaceholderNameKey, PlaceholderNameValue }
        );
}

class CPairInstance : public ITypeInstance
{
protected:
    CPairGeneralized* Type;
public:
    CPairInstance(CPairGeneralized* InType)
        :Type(InType)
    {
    }

    void InitInstance(void* Memory) override
    {
        Type->GetKeyType().BasicType->InitInstance(Memory);
        Type->GetValueType().BasicType->InitInstance((uint8_t*)Memory+Type->GetValueOffset());
    }

    void CloneInstance(const void* Instance, void* Memory) override
    {
        Type->GetKeyType().BasicType->CloneInstance(Instance,Memory);
        Type->GetValueType().BasicType->CloneInstance((uint8_t*)Instance+Type->GetValueOffset(),(uint8_t*)Memory+Type->GetValueOffset());
    }

    void EraseInstance(void* InValue) override
    {
        Type->GetKeyType().BasicType->EraseInstance(InValue);
        Type->GetValueType().BasicType->EraseInstance((uint8_t*)InValue+Type->GetValueOffset());
    }

    void* CreateInstance() override
    {
        void* Memory=malloc(Type->GetSize());
        Type->GetKeyType().BasicType->InitInstance(Memory);
        Type->GetValueType().BasicType->InitInstance((uint8_t*)Memory+Type->GetValueOffset());
        return Memory;
    }

    void ResetAllocated(void* InMemory) override
    {
        Type->GetKeyType().BasicType->ResetAllocated(InMemory);
        Type->GetValueType().BasicType->ResetAllocated((uint8_t*)InMemory+Type->GetValueOffset());
    }

    uint32_t GetSize() override
    {
        return Type->GetValueOffset()+ Type->GetValueType().BasicType->GetSize();
    }

    uint64_t GetInstanceHash(const void* InInstance) const override
    {
        return WH::HashCombine(
                Type->GetKeyType().BasicType->GetInstanceHash(InInstance)
                ,Type->GetValueType().BasicType->GetInstanceHash((uint8_t*)InInstance+Type->GetValueOffset()));
    }

    bool IsInstanceEqual(const void* A, const void* B) const override
    {
        return Type->GetKeyType().BasicType->IsInstanceEqual(A,B) 
        && Type->GetValueType().BasicType->IsInstanceEqual((uint8_t*)A+Type->GetValueOffset(),(uint8_t*)B+Type->GetValueOffset());
    }

    bool IsForceConvertableTo( CType*  Other) override
    {
        if(!Other->IsPairType())
        {
            return false;
        }

        CPairGeneralized* OtherPairType = static_cast< CPairGeneralized*>(Other);
        return Type->GetKeyType().BasicType->IsForceConvertableTo(OtherPairType->GetKeyType().BasicType)
         && Type->GetValueType().BasicType->IsForceConvertableTo(OtherPairType->GetValueType().BasicType);
    }

    CString ToDebugString(const void* InInstance) const override
    {
        return Type->GetFullName();
    }
};

class CPairGC : public IGarbageCollectable
{
protected:
    CPairGeneralized* Type;
public:
    CPairGC(CPairGeneralized* InType)
        :Type(InType)
    {
    }

    virtual void CollectReferencedObjects(
        void* InInstance,
        TForwardForwardLinkedList<RObject*>& OutReferencedObjects
    ) override
    {
        auto KeyType=Type->GetKeyType().BasicType;
        if(KeyType->GarbageCollectable)
        {
            KeyType->GarbageCollectable->CollectReferencedObjects(InInstance,OutReferencedObjects);
        }

        auto ValueType=Type->GetValueType().BasicType;
        if(ValueType->GarbageCollectable)
        {
            auto Value= (uint8_t*)InInstance+Type->GetValueOffset();
            ValueType->GarbageCollectable->CollectReferencedObjects(Value,OutReferencedObjects);
        }
    }
};

CPairGeneralized::CPairGeneralized(
    const SDecoratedType& InKeyType,
    const SDecoratedType& InValueType,
    int64_t InValueOffset)
    :CType(
        CPairType::CreateID(InKeyType,InValueType)
        ,ETypeFlags::Pair | ETypeFlags::Generalized
         | (InKeyType.BasicType->CanUseInScript() && InValueType.BasicType->CanUseInScript()? ETypeFlags::None: ETypeFlags::CantUseInScript )
    )
{
    ValueOffset = InValueOffset;
    if(ValueOffset==0)
    {
        ValueOffset = InKeyType.BasicType->GetSize();
    }

    Instancable=new CPairInstance(this);
    if(InKeyType.BasicType->GarbageCollectable || InValueType.BasicType->GarbageCollectable)
    {
        GarbageCollectable=new CPairGC(this);
    }

    GeneralizedInfo=new CGeneralizedInfo();
    GeneralizedInfo->TemplateType=CPairType::GetStaticType();
    GeneralizedInfo->TypeArguments.Add(InKeyType.BasicType);
    GeneralizedInfo->TypeArguments.Add(InValueType.BasicType);

    //add fields
    Properties = new CPropertyList(this);
    {
        //key field
        CProperty* KeyProperty = new CNativeFieldProperty(
            this->Properties
            ,{}
            // ,[InKeyType](const void *  Instance) -> CAny 
            //     { 
            //         return CAny(InKeyType,(void*)Instance,true);
            //     }
            // ,[InKeyType](void *  Instance, const CAny& Value) -> void 
            //     { 
            //         InKeyType->EraseInstance(Instance);
            //         InKeyType->CloneInstance(Value.GetPointer(),Instance);
            //     }
            ,InKeyType
            ,U"Key"
            ,0
            ,0
        );

        Properties->AddProperty(KeyProperty);
    }

    {
        //value field
        CProperty* ValueProperty = new CNativeFieldProperty(
            this->Properties
            ,{}
            // ,[InKeyType,InValueType](const void *  Instance) -> CAny 
            //     {
            //         return CAny(InValueType,(uint8_t*)Instance+InKeyType->GetSize(),true);
            //     }
            // ,[InKeyType,InValueType](void *  Instance, const CAny& Value) -> void 
            //     {
            //         InValueType->EraseInstance((uint8_t*)Instance+InKeyType->GetSize());
            //         InValueType->CloneInstance(Value.GetPointer(),(uint8_t*)Instance+InKeyType->GetSize());
            //     }
            ,InValueType
            ,U"Value"
            ,InKeyType.BasicType->GetSize()
            ,1
        );

        Properties->AddProperty(ValueProperty);
    }
}

const SDecoratedType& CPairGeneralized::GetKeyType()
{
    return GeneralizedInfo->TypeArguments[0];
}

const SDecoratedType& CPairGeneralized::GetValueType()
{
    return GeneralizedInfo->TypeArguments[1];
}

CPairGeneralized *CPairGeneralized::FindPairType(const SDecoratedType& InKeyType, const SDecoratedType& InValueType)
{
    CPairType* PairType = CPairType::GetStaticType();

    CType ** Found= reinterpret_cast<CGenericInfo*>(PairType->GenericInfo)->GenericMap
        .Find({InKeyType.BasicType,InValueType.BasicType});

    if(Found)
    {
        return static_cast<CPairGeneralized*>(*Found);
    }
    else
    {
        return nullptr;
    }
}

CPairGeneralized *CPairGeneralized::FindOrAddType(
    const SDecoratedType& InKeyType
    ,const SDecoratedType& InValueType
    ,int64_t InValueOffset
    )
{
    CPairType* PairType = CPairType::GetStaticType();

    if(!reinterpret_cast<CGenericInfo*>(PairType->GenericInfo)->GenericMap
        .Contains({InKeyType.BasicType,InValueType.BasicType}))
    {
        CPairGeneralized* NewPairType = new CPairGeneralized(
            InKeyType
            ,InValueType
            ,InValueOffset
            );
        reinterpret_cast<CGenericInfo*>(PairType->GenericInfo)->GenericMap
            .Add(TVector<CType*>({InKeyType.BasicType,InValueType.BasicType}),NewPairType);

        return NewPairType;
    }

    CType **Found = reinterpret_cast<CGenericInfo*>(PairType->GenericInfo)->GenericMap
        .Find({InKeyType.BasicType,InValueType.BasicType});

    return static_cast<CPairGeneralized*>(*Found);

}

uint64_t CPairGeneralized::GetKeyHash(const void *InPair)
{
    return GetKeyType().BasicType->GetInstanceHash(InPair);
}

void CPairGeneralized::EraseValue(void *InPair)
{
    GetValueType().BasicType->EraseInstance((uint8_t*)InPair+ValueOffset);
}

void CPairGeneralized::CloneValue(const void *InSrc, void *OutDest)
{
    GetValueType().BasicType->CloneInstance((uint8_t *)InSrc + ValueOffset, (uint8_t *)OutDest + ValueOffset);
}

void CPairGeneralized::CloneKeyAndValue(const void *InScrKey, const void *InScrValue, void *InDestPair)
{
    GetKeyType().BasicType->CloneInstance(InScrKey,InDestPair);
    GetValueType().BasicType->CloneInstance(InScrValue,(uint8_t*)InDestPair+ValueOffset);
}

void *CPairGeneralized::GetKeyPtr(void *InPair)
{
    return InPair;
}

void *CPairGeneralized::GetValuePtr(const void *InPair)
{
    return (uint8_t*)InPair+ValueOffset;
}

bool CPairGeneralized::IsKeyEqual(const void *InPair1, const void *InPair2)
{
    return GetKeyType().BasicType->IsInstanceEqual(InPair1,InPair2);
}
bool CPairGeneralized::IsValueEqual(const void *InPair1, const void *InPair2)
{
    return GetValueType().BasicType->IsInstanceEqual(
        (uint8_t*)InPair1+ValueOffset
        ,(uint8_t*)InPair2+ValueOffset
    );

}

static uint8_t RegisterTypesCaller=(CPairType::GetStaticType(),0);